package com.tsfyun.scm.service.impl.logistics;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.tsfyun.common.base.dto.*;
import com.tsfyun.common.base.enums.*;
import com.tsfyun.common.base.enums.domain.*;
import com.tsfyun.common.base.enums.logistics.CarTypeEnum;
import com.tsfyun.common.base.enums.logistics.TransportDestinationEnum;
import com.tsfyun.common.base.enums.logistics.WaybillBusinessTypeEnum;
import com.tsfyun.common.base.exception.ServiceException;
import com.tsfyun.common.base.security.SecurityUtil;
import com.tsfyun.common.base.support.DomainStatus;
import com.tsfyun.common.base.util.DateUtils;
import com.tsfyun.common.base.util.LocalDateTimeUtils;
import com.tsfyun.common.base.util.StringUtils;
import com.tsfyun.common.base.util.TsfPreconditions;
import com.tsfyun.scm.dto.customs.WaybillBindOrderUpdateDecDTO;
import com.tsfyun.scm.dto.logistics.*;
import com.tsfyun.scm.dto.support.TaskNoticeContentDTO;
import com.tsfyun.scm.entity.customs.Declaration;
import com.tsfyun.scm.entity.logistics.Conveyance;
import com.tsfyun.scm.entity.logistics.CrossBorderWaybill;
import com.tsfyun.scm.entity.logistics.TransportSupplier;
import com.tsfyun.scm.entity.order.ExpOrder;
import com.tsfyun.scm.entity.order.ImpOrder;
import com.tsfyun.scm.entity.storage.Warehouse;
import com.tsfyun.scm.entity.user.Person;
import com.tsfyun.scm.mapper.logistics.CrossBorderWaybillMapper;
import com.tsfyun.scm.service.common.ICommonService;
import com.tsfyun.scm.service.customs.IDeclarationService;
import com.tsfyun.scm.service.logistics.IConveyanceService;
import com.tsfyun.scm.service.logistics.ICrossBorderWaybillService;
import com.tsfyun.common.base.extension.ServiceImpl;
import com.tsfyun.scm.service.logistics.IDeliveryWaybillService;
import com.tsfyun.scm.service.logistics.ITransportSupplierService;
import com.tsfyun.scm.service.order.IExpOrderService;
import com.tsfyun.scm.service.order.IImpOrderService;
import com.tsfyun.scm.service.storage.IWarehouseService;
import com.tsfyun.scm.service.support.ITaskNoticeContentService;
import com.tsfyun.scm.service.system.ISerialNumberService;
import com.tsfyun.scm.service.system.IStatusHistoryService;
import com.tsfyun.scm.service.third.IShortMessageService;
import com.tsfyun.scm.service.third.IWxMessageService;
import com.tsfyun.scm.service.user.IPersonService;
import com.tsfyun.scm.service.wms.IDeliveryNoteService;
import com.tsfyun.scm.vo.logistics.CrossBorderWaybillVO;
import com.tsfyun.scm.vo.order.BindOrderVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors;

/**
 * <p>
 * 跨境运输单 服务实现类
 * </p>
 *

 * @since 2020-04-17
 */
@RefreshScope
@Slf4j
@Service
public class CrossBorderWaybillServiceImpl extends ServiceImpl<CrossBorderWaybill> implements ICrossBorderWaybillService {

    @Autowired
    private CrossBorderWaybillMapper crossBorderWaybillMapper;
    @Autowired
    private ISerialNumberService serialNumberService;
    @Autowired
    private IWarehouseService warehouseService;
    @Autowired
    private ITransportSupplierService transportSupplierService;
    @Autowired
    private IConveyanceService conveyanceService;
    @Autowired
    private IStatusHistoryService statusHistoryService;
    @Autowired
    private ICommonService commonService;
    @Autowired
    private ITaskNoticeContentService taskNoticeContentService;
    @Autowired
    private RedisLockRegistry redisLockRegistry;
    @Autowired
    private IImpOrderService impOrderService;
    @Autowired
    private IExpOrderService expOrderService;
    @Autowired
    IDeclarationService declarationService;
    @Autowired
    private IDeliveryNoteService deliveryNoteService;
    @Value("${redis.lock.time:5}")
    private Integer lockTime;
    @Autowired
    private IDeliveryWaybillService deliveryWaybillService;
    @Autowired
    private IShortMessageService shortMessageService;
    @Autowired
    private IPersonService personService;
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    private IWxMessageService wxMessageService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long add(CrossBorderWaybillDTO dto) {
        CrossBorderWaybill crossBorderWaybill = new CrossBorderWaybill( );
        checkWrap(dto,crossBorderWaybill);
        //生成单号
        crossBorderWaybill.setDocNo(serialNumberService.generateDocNo(SerialNumberTypeEnum.IMP_CROSS_BORDER_WAYBILL));
        //待确定
        CrossBorderWaybillStatusEnum statusEnum = CrossBorderWaybillStatusEnum.WAIT_CONFIRM;
        crossBorderWaybill.setStatusId(statusEnum.getCode());
        try {
            super.saveNonNull(crossBorderWaybill);
        } catch (DuplicateKeyException e) {
            throw new ServiceException("系统单号已经被占用，请稍后再试");
        }
        DomainTypeEnum domainTypeEnum = DomainTypeEnum.CROSSBORDERWAYBILL;
        statusHistoryService.saveHistory(
                DomainOprationEnum.CROSS_BORDER_WAYBILL_ADD,crossBorderWaybill.getId().toString(),
                domainTypeEnum.getCode(),
                statusEnum.getCode(),statusEnum.getName(),"新增跨境运输单");
        //任务通知
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        taskNoticeContentDTO.setDocumentType(domainTypeEnum.getCode());
        taskNoticeContentDTO.setDocumentId(crossBorderWaybill.getId().toString());
        taskNoticeContentDTO.setCustomerId(0L);
        DomainOprationEnum oprationEnum = BillTypeEnum.IMP.getCode().equals(crossBorderWaybill.getBillType())?DomainOprationEnum.CROSS_BORDER_WAYBILL_CONFIRM:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_CONFIRM;
        taskNoticeContentDTO.setOperationCode(oprationEnum.getCode());
        taskNoticeContentDTO.setContent(String.format("【%s】提交了一单【%s】跨境运输单需要您确认。", SecurityUtil.getCurrentPersonName(), crossBorderWaybill.getDocNo()));
        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",crossBorderWaybill.getDocNo()));
        taskNoticeContentService.add(taskNoticeContentDTO);
        return crossBorderWaybill.getId();
    }
    //填充验证数据
    public void checkWrap(CrossBorderWaybillDTO dto,CrossBorderWaybill crossBorderWaybill) {
        crossBorderWaybill.setBillType(dto.getBillType());
        crossBorderWaybill.setTransDate(dto.getTransDate());
        crossBorderWaybill.setWaybillNo(dto.getWaybillNo());
        crossBorderWaybill.setCustomsCode(dto.getCustomsCode());
        crossBorderWaybill.setCustomsName(dto.getCustomsName());
        //运输供应商
        TransportSupplier transportSupplier = transportSupplierService.getById(dto.getTransportSupplierId());
        TsfPreconditions.checkArgument(Objects.nonNull(transportSupplier) && Objects.equals(transportSupplier.getDisabled(),Boolean.FALSE),new ServiceException("运输供应商不存在或已被禁用"));
        crossBorderWaybill.setTransportSupplierId(transportSupplier.getId());
        //运输工具
        Conveyance conveyance = conveyanceService.getById(dto.getConveyanceId());
        TsfPreconditions.checkArgument(Objects.nonNull(conveyance) && Objects.equals(conveyance.getDisabled(),Boolean.FALSE),new ServiceException("运输工具不存在或已被禁用"));
        crossBorderWaybill.setConveyanceId(conveyance.getId());
        crossBorderWaybill.setConveyanceNo(conveyance.getConveyanceNo());
        crossBorderWaybill.setConveyanceNo2(conveyance.getConveyanceNo2());
        //司机信息
        crossBorderWaybill.setDriverInfo(String.format("%s %s %s",StringUtils.null2EmptyWithTrim(conveyance.getDriver()),StringUtils.null2EmptyWithTrim(conveyance.getDriverTel()), StringUtils.null2EmptyWithTrim(conveyance.getDriverNo())));
        //订车类型
        CarTypeEnum carTypeEnum = CarTypeEnum.of(dto.getConveyanceType());
        crossBorderWaybill.setConveyanceType(carTypeEnum.getCode());
        //集装箱号
        crossBorderWaybill.setContainerNo(dto.getContainerNo());
        if(Objects.nonNull(carTypeEnum) && CarTypeEnum.isCabinet(carTypeEnum.getCode())  && StringUtils.isEmpty(crossBorderWaybill.getContainerNo())){
            throw new ServiceException("柜车请输入集装箱号");
        }
        //送货目地地
        TransportDestinationEnum transportDestinationEnum = TransportDestinationEnum.of(dto.getTransportDestination());
        crossBorderWaybill.setTransportDestination(transportDestinationEnum.getCode());
        //发货仓库
        Warehouse shippingWarehouse = warehouseService.getById(dto.getShippingWareId());
        TsfPreconditions.checkArgument(Objects.nonNull(shippingWarehouse) && Objects.equals(shippingWarehouse.getDisabled(),Boolean.FALSE),new ServiceException("发货仓库不存在或已被禁用"));
        crossBorderWaybill.setShippingWareName(shippingWarehouse.getName());
        crossBorderWaybill.setShippingLinkPerson(shippingWarehouse.getLinkPerson());
        crossBorderWaybill.setShippingCompany(shippingWarehouse.getCompanyName());
        crossBorderWaybill.setShippingLinkTel(shippingWarehouse.getLinkTel());
        crossBorderWaybill.setShippingAddress(shippingWarehouse.getAddress());
        //收货信息
        if(Objects.equals(transportDestinationEnum,TransportDestinationEnum.WAREHOUSE)) {
            //货运仓库
            TsfPreconditions.checkArgument(StringUtils.isNotEmpty(dto.getDeliveryWareId()),new ServiceException("请选择收货仓库"));
            Warehouse deliveryWarehouse = warehouseService.getById(dto.getDeliveryWareId());
            TsfPreconditions.checkArgument(Objects.nonNull(deliveryWarehouse) && Objects.equals(deliveryWarehouse.getDisabled(),Boolean.FALSE),new ServiceException("收货仓库不存在或已被禁用"));
            crossBorderWaybill.setDeliveryWareId(deliveryWarehouse.getId());
            crossBorderWaybill.setDeliveryWareName(deliveryWarehouse.getName());
            crossBorderWaybill.setDeliveryCompany(deliveryWarehouse.getCompanyName());
            crossBorderWaybill.setDeliveryLinkPerson(deliveryWarehouse.getLinkPerson());
            crossBorderWaybill.setDeliveryLinkTel(deliveryWarehouse.getLinkTel());
            crossBorderWaybill.setDeliveryAddress(deliveryWarehouse.getAddress());
        } else if(Objects.equals(transportDestinationEnum,TransportDestinationEnum.DESTINATION)) {
            TsfPreconditions.checkArgument(StringUtils.isNotEmpty(dto.getDeliveryCompany()),new ServiceException("请输入收货公司名称"));
            TsfPreconditions.checkArgument(StringUtils.isNotEmpty(dto.getDeliveryLinkPerson()),new ServiceException("请输入收货联系人"));
            TsfPreconditions.checkArgument(StringUtils.isNotEmpty(dto.getDeliveryLinkTel()),new ServiceException("请输入收货联系电话"));
            TsfPreconditions.checkArgument(StringUtils.isNotEmpty(dto.getDeliveryAddress()),new ServiceException("请输入收货联系地址"));
            crossBorderWaybill.setDeliveryWareId(null);
            crossBorderWaybill.setDeliveryWareName(null);
            crossBorderWaybill.setDeliveryCompany(dto.getDeliveryCompany());
            crossBorderWaybill.setDeliveryLinkPerson(dto.getDeliveryLinkPerson());
            crossBorderWaybill.setDeliveryLinkTel(dto.getDeliveryLinkTel());
            crossBorderWaybill.setDeliveryAddress(dto.getDeliveryAddress());
        }
        crossBorderWaybill.setMemo(dto.getMemo());
        //一般贸易进口
        crossBorderWaybill.setWaybillBusinessType(WaybillBusinessTypeEnum.GENERAL_TRADE_IMPORT.getCode());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void edit(CrossBorderWaybillDTO dto) {
        CrossBorderWaybill crossBorderWaybill = super.getById(dto.getId());
        TsfPreconditions.checkArgument(Objects.nonNull(crossBorderWaybill),new ServiceException("运单不存在"));
        CrossBorderWaybillStatusEnum historyStatus = CrossBorderWaybillStatusEnum.of(crossBorderWaybill.getStatusId());
        DomainOprationEnum oprationEnum = BillTypeEnum.IMP.getCode().equals(dto.getBillType())?DomainOprationEnum.CROSS_BORDER_WAYBILL_EDIT:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_EDIT;
        //验证状态是否可以操作
        DomainStatus.getInstance().check(oprationEnum,crossBorderWaybill.getStatusId());
        checkWrap(dto,crossBorderWaybill);
        CrossBorderWaybillStatusEnum statusEnum = CrossBorderWaybillStatusEnum.of(crossBorderWaybill.getStatusId());
        //修改
        super.updateById(crossBorderWaybill);
        //记录历史状态
        statusHistoryService.saveHistory(oprationEnum,
                crossBorderWaybill.getId().toString(),CrossBorderWaybill.class.getName(),
                historyStatus.getCode(),historyStatus.getName(),
                statusEnum.getCode(),statusEnum.getName(),
                "修改跨境运输单"
        );
    }

    @Override
    public PageInfo<CrossBorderWaybillVO> pageList(CrossBorderWaybillQTO qto) {
        PageHelper.startPage(qto.getPage(),qto.getLimit());
        List<CrossBorderWaybillVO> crossBorderWaybills =  crossBorderWaybillMapper.list(qto);
        return new PageInfo<>(crossBorderWaybills);
    }

    @Override
    public CrossBorderWaybillVO detail(Long id,String operation) {
        CrossBorderWaybillVO crossBorderWaybill = crossBorderWaybillMapper.detail(id);
        TsfPreconditions.checkArgument(Objects.nonNull(crossBorderWaybill),new ServiceException("数据不存在"));
        DomainStatus.getInstance().check(DomainOprationEnum.of(operation),crossBorderWaybill.getStatusId());
        return crossBorderWaybill;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(Long id) {
        CrossBorderWaybill crossBorderWaybill = super.getById(id);
        TsfPreconditions.checkArgument(Objects.nonNull(crossBorderWaybill),new ServiceException("数据不存在"));
        DomainOprationEnum oprationEnum = BillTypeEnum.IMP.getCode().equals(crossBorderWaybill.getBillType())?DomainOprationEnum.CROSS_BORDER_WAYBILL_DELETE:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_DELETE;
        DomainStatus.getInstance().check(oprationEnum,crossBorderWaybill.getStatusId());
        try {
            super.removeById(id);
        } catch (DataIntegrityViolationException e) {
            throw new ServiceException("当前运输单据存在关联性数据，不允许删除");
        }
        //清除修改任务提示
        clearTaskNotice(id);
    }

    //清空任务通知
    public void clearTaskNotice(Long id){
        taskNoticeContentService.deleteTaskNotice(DomainTypeEnum.CROSSBORDERWAYBILL.getCode(), id, "");
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void comfirm(TaskDTO dto) {
        TsfPreconditions.checkArgument(Objects.equals(DomainTypeEnum.CROSSBORDERWAYBILL.getCode(),dto.getDocumentClass()),new ServiceException("单据类型错误"));
        //操作后的状态
        CrossBorderWaybillStatusEnum afterDoneStatus = CrossBorderWaybillStatusEnum.of(dto.getNewStatusCode());
        TsfPreconditions.checkArgument(Objects.equals(CrossBorderWaybillStatusEnum.WAIT_BIND,afterDoneStatus),new ServiceException("当前状态不允许此操作"));
        CrossBorderWaybill crossBorderWaybill = commonService.changeDocumentStatus(dto);
        //任务通知
        TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
        taskNoticeContentDTO.setDocumentType(DomainTypeEnum.CROSSBORDERWAYBILL.getCode());
        taskNoticeContentDTO.setDocumentId(crossBorderWaybill.getId().toString());
        taskNoticeContentDTO.setCustomerId(0L);//未知客户
        DomainOprationEnum oprationEnum = BillTypeEnum.IMP.getCode().equals(crossBorderWaybill.getBillType())?DomainOprationEnum.CROSS_BORDER_WAYBILL_BIND:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_BIND;
        taskNoticeContentDTO.setOperationCode(oprationEnum.getCode());
        taskNoticeContentDTO.setContent(String.format("跨境运输单【%s】已完成确认，请尽快绑单。", crossBorderWaybill.getDocNo()));
        taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",crossBorderWaybill.getDocNo()));
        taskNoticeContentService.add(taskNoticeContentDTO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void bindOrder(CrossBorderWaybillBindOrderDTO dto) {
        String lockKey = DistributedLockEnum.CROSSBORDER_BIND_ORDER.getCode() + ":"  + dto.getId()+":"+dto.getOrderId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("运输单绑定订单未获取到锁，运输单id：【%s】，操作人：【{}】", dto.getId()),SecurityUtil.getCurrentPersonName());
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            CrossBorderWaybill crossBorderWaybill = super.getById(dto.getId());
            Optional.ofNullable(crossBorderWaybill).orElseThrow(()->new ServiceException("跨境运输单数据不存在，请确认是否已经被删除"));
            CrossBorderWaybillStatusEnum crossBorderWaybillStatusEnum = CrossBorderWaybillStatusEnum.of(crossBorderWaybill.getStatusId());
            TsfPreconditions.checkArgument(Objects.equals(crossBorderWaybillStatusEnum,CrossBorderWaybillStatusEnum.WAIT_BIND),new ServiceException("运输单当前状态不允许此操作,请刷新页面重试"));

            switch (BillTypeEnum.of(crossBorderWaybill.getBillType())) {
                case IMP:// 进口
                    ImpOrder impOrder = impOrderService.getById(dto.getOrderId());
                    TsfPreconditions.checkArgument(Objects.nonNull(impOrder),new ServiceException("订单不存在"));
                    TsfPreconditions.checkArgument(StringUtils.isEmpty(impOrder.getTransportNo()),new ServiceException(String.format("当前订单已被运单【%s】绑定，请重新选择",impOrder.getTransportNo())));
                    int updateImp = impOrderService.updateTransportNo(dto.getOrderId(),crossBorderWaybill.getDocNo());
                    if(1 != updateImp) {
                        throw new ServiceException(String.format("订单【%s】还未确定进口或已经被其他运输单绑定，请重新选择",impOrder.getDocNo()));
                    }
                    break;
                case EXP:// 出口
                    ExpOrder expOrder = expOrderService.getById(dto.getOrderId());
                    TsfPreconditions.checkArgument(Objects.nonNull(expOrder),new ServiceException("订单不存在"));
                    TsfPreconditions.checkArgument(StringUtils.isEmpty(expOrder.getTransportNo()),new ServiceException(String.format("当前订单已被运单【%s】绑定，请重新选择",expOrder.getTransportNo())));
                    int updateExp = expOrderService.updateTransportNo(dto.getOrderId(),crossBorderWaybill.getDocNo());
                    if(1 != updateExp) {
                        throw new ServiceException(String.format("订单【%s】还未确定出口或已经被其他运输单绑定，请重新选择",expOrder.getDocNo()));
                    }
                    break;
                default:
                    throw new ServiceException("单据类型错误");
            }
            //如果存在报关单，则检查报关单上的进境关别和跨境运输单是否一致，不一致不能绑定
            Declaration declaration = declarationService.getByOrderId(dto.getOrderId());
            if(Objects.nonNull(declaration) && StringUtils.isNotEmpty(declaration.getIePortCode())) {
                log.info("报关单【{}】进境关别【{}】【{}】",declaration.getDocNo(),declaration.getIePortCode(),declaration.getIePortName());
                TsfPreconditions.checkArgument(Objects.equals(declaration.getIePortCode(),crossBorderWaybill.getCustomsCode()),new ServiceException(String.format("订单对应的报关单进境关别【%s】与运输单通关口岸【%s】不一致，不能绑定到该运输单",declaration.getIePortName(),crossBorderWaybill.getCustomsName())));
            }
            //更新订单对应的报关单数据
            WaybillBindOrderUpdateDecDTO waybillBindOrderUpdateDecDTO =  new WaybillBindOrderUpdateDecDTO()
                    .setOrderId(dto.getOrderId())
                    .setTransportNo(crossBorderWaybill.getDocNo())
                    .setContainerNo(crossBorderWaybill.getContainerNo())
                    .setCusVoyageNo(crossBorderWaybill.getWaybillNo())
                    .setLicensePlate(crossBorderWaybill.getConveyanceNo());
            declarationService.updateByWaybill(waybillBindOrderUpdateDecDTO);
        } catch (InterruptedException e) {
            log.error("跨境运输单绑定订单获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            lock.unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void unbindOrder(CrossBorderWaybill crossBorderWaybill, Long orderId) {
        //此处不需要加锁，存在退会修改批量解除情况
        Optional.ofNullable(crossBorderWaybill).orElseThrow(()->new ServiceException("跨境运输单数据不存在，请确认是否已经被删除"));
        CrossBorderWaybillStatusEnum crossBorderWaybillStatusEnum = CrossBorderWaybillStatusEnum.of(crossBorderWaybill.getStatusId());
        TsfPreconditions.checkArgument(Objects.equals(crossBorderWaybillStatusEnum,CrossBorderWaybillStatusEnum.WAIT_BIND),new ServiceException("当前状态不允许此操作"));
        // 根据订单获取报关单
        Declaration declaration = declarationService.findByOrderId(orderId);
        if(Objects.nonNull(declaration)){
            // 报关单不能处于申报中或已完成状态
            DeclarationStatusEnum declarationStatusEnum = DeclarationStatusEnum.of(declaration.getStatusId());
            if(Objects.equals(declarationStatusEnum,DeclarationStatusEnum.IN_DECLARE) || Objects.equals(declarationStatusEnum,DeclarationStatusEnum.COMPLETE)){
                throw new ServiceException(String.format("报关单【%s】处于申报中或已完成状态，请先联系关务人员退回",declaration.getDocNo()));
            }
        }
        switch (BillTypeEnum.of(crossBorderWaybill.getBillType())){
            case IMP:// 进口
                ImpOrder impOrder = impOrderService.getById(orderId);
                Optional.ofNullable(impOrder).orElseThrow(()->new ServiceException(String.format("您选择的需要解绑的订单不存在")));
                TsfPreconditions.checkArgument(Objects.equals(impOrder.getTransportNo(),crossBorderWaybill.getDocNo()),new ServiceException(String.format("您选择解绑的订单【%s】已被运输单【%s】绑定，请重新选择",impOrder.getDocNo(),impOrder.getTransportNo())));
                //订单跨境运单号置为空
                impOrderService.resetTransportNo(impOrder.getId());
                break;
            case EXP:// 出口
                ExpOrder expOrder = expOrderService.getById(orderId);
                Optional.ofNullable(expOrder).orElseThrow(()->new ServiceException(String.format("您选择的需要解绑的订单不存在")));
                TsfPreconditions.checkArgument(Objects.equals(expOrder.getTransportNo(),crossBorderWaybill.getDocNo()),new ServiceException(String.format("您选择解绑的订单【%s】已被运输单【%s】绑定，请重新选择",expOrder.getDocNo(),expOrder.getTransportNo())));
                //订单跨境运单号置为空
                expOrderService.resetTransportNo(expOrder.getId());
                break;
            default:
                throw new ServiceException("单据类型错误");
        }

        //更新订单对应的报关单数据
        WaybillBindOrderUpdateDecDTO waybillBindOrderUpdateDecDTO =  new WaybillBindOrderUpdateDecDTO()
                .setOrderId(orderId)
                .setTransportNo("")
                .setContainerNo("")
                .setCusVoyageNo("")
                .setLicensePlate("");
        declarationService.updateByWaybill(waybillBindOrderUpdateDecDTO);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void unbindOrder(Long id, Long orderId) {
        //解绑操作由于操作不频繁，不使用分布式锁
        CrossBorderWaybill crossBorderWaybill = super.getById(id);
        unbindOrder(crossBorderWaybill,orderId);
    }

    @Override
    public List<BindOrderVO> canBindList(Long id) {
        CrossBorderWaybill crossBorderWaybill = super.getById(id);
        Optional.ofNullable(crossBorderWaybill).orElseThrow(()->new ServiceException("跨境运输单数据不存在，请确认是否已经被删除"));
        switch (BillTypeEnum.of(crossBorderWaybill.getBillType())){
            case IMP:
                return impOrderService.waybillCanBindOrderList(Objects.equals(TransportDestinationEnum.DESTINATION.getCode(),crossBorderWaybill.getTransportDestination()));
            case EXP:
                return expOrderService.waybillCanBindOrderList(Objects.equals(TransportDestinationEnum.DESTINATION.getCode(),crossBorderWaybill.getTransportDestination()));
            default:
                throw new ServiceException("单据类型错误");
        }
    }

    @Override
    public List<BindOrderVO> bindedList(CrossBorderWaybill crossBorderWaybill) {
        Optional.ofNullable(crossBorderWaybill).orElseThrow(()->new ServiceException("跨境运输单数据不存在，请确认是否已经被删除"));
        switch (BillTypeEnum.of(crossBorderWaybill.getBillType())){
            case IMP:
                return impOrderService.waybillBindedOrderList(crossBorderWaybill.getDocNo());
            case EXP:
                return expOrderService.waybillBindedOrderList(crossBorderWaybill.getDocNo());
            default:
                throw new ServiceException("单据类型错误");
        }

    }

    @Override
    public List<BindOrderVO> bindedList(Long id) {
        CrossBorderWaybill crossBorderWaybill = super.getById(id);
       return bindedList(crossBorderWaybill);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void bindComplete(TaskDTO dto){
        String lockKey = DistributedLockEnum.CROSSBORDER_BIND.getCode() + ":" +  dto.getDocumentId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("【%s】运输单绑单未获取到锁，运输单id：【%s】，操作人：【{}】", dto.getDocumentId()),SecurityUtil.getCurrentPersonName());
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            TsfPreconditions.checkArgument(Objects.equals(DomainTypeEnum.CROSSBORDERWAYBILL.getCode(),dto.getDocumentClass()),new ServiceException("单据类型错误"));
            CrossBorderWaybill crossBorderWaybill = commonService.changeDocumentStatus(dto);
            BillTypeEnum billTypeEnum = BillTypeEnum.of(crossBorderWaybill.getBillType());
            switch (billTypeEnum){
                case IMP:
                    //检查该运输单是否绑单订单
                    int impOrderCount = impOrderService.getWaybillBindOrderNumber(crossBorderWaybill.getDocNo());
                    TsfPreconditions.checkArgument(impOrderCount > 0,new ServiceException("请至少绑定一个订单"));
                    break;
                case EXP:
                    //检查该运输单是否绑单订单
                    int expOrderCount = expOrderService.getWaybillBindOrderNumber(crossBorderWaybill.getDocNo());
                    TsfPreconditions.checkArgument(expOrderCount > 0,new ServiceException("请至少绑定一个订单"));
                    break;
                default:
                    throw new ServiceException("单据类型错误");
            }
            // 清空任务通知
            clearTaskNotice(crossBorderWaybill.getId());
            //发送通知
            TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
            taskNoticeContentDTO.setDocumentType(DomainTypeEnum.CROSSBORDERWAYBILL.getCode());
            taskNoticeContentDTO.setDocumentId(crossBorderWaybill.getId().toString());
            taskNoticeContentDTO.setCustomerId(0L); //未知客户
            DomainOprationEnum oprationEnum = Objects.equals(billTypeEnum,BillTypeEnum.IMP)?DomainOprationEnum.CROSS_BORDER_WAYBILL_REVIRE:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_REVIRE;
            taskNoticeContentDTO.setOperationCode(oprationEnum.getCode());
            taskNoticeContentDTO.setContent(String.format("【%s】提交了一份跨境运输单【%s】需要您复核", SecurityUtil.getCurrentPersonName(),crossBorderWaybill.getDocNo()));
            taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",crossBorderWaybill.getDocNo()));
            taskNoticeContentService.add(taskNoticeContentDTO);
        } catch (InterruptedException e) {
            log.error("跨境运输单绑单操作获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            lock.unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void backUpdate(TaskDTO dto) {
        String lockKey = DistributedLockEnum.CROSSBORDER_BIND_ORDER.getCode() + ":"  + dto.getDocumentId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("运输单退回修改未获取到锁，运输单id：【%s】，操作人：【{}】", dto.getDocumentId()),SecurityUtil.getCurrentPersonName());
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            CrossBorderWaybill crossBorderWaybill = getById(dto.getDocumentId());
            //解绑所有订单和报关单
            List<BindOrderVO> bindOrderVOS = bindedList(crossBorderWaybill);
            if(CollUtil.isNotEmpty(bindOrderVOS)){
                bindOrderVOS.stream().forEach(bo ->{
                    unbindOrder(crossBorderWaybill,bo.getId());
                });
            }
            commonService.changeDocumentStatus(dto);
            //任务通知
            TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
            taskNoticeContentDTO.setDocumentType(DomainTypeEnum.CROSSBORDERWAYBILL.getCode());
            taskNoticeContentDTO.setDocumentId(crossBorderWaybill.getId().toString());
            taskNoticeContentDTO.setCustomerId(0L);
            DomainOprationEnum oprationEnum = BillTypeEnum.IMP.getCode().equals(crossBorderWaybill.getBillType())?DomainOprationEnum.CROSS_BORDER_WAYBILL_CONFIRM:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_CONFIRM;
            taskNoticeContentDTO.setOperationCode(oprationEnum.getCode());
            taskNoticeContentDTO.setContent(String.format("跨境运输单【%s】已被【%s】退回需要您重新确认。", crossBorderWaybill.getDocNo(),SecurityUtil.getCurrentPersonName()));
            taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",crossBorderWaybill.getDocNo()));
            taskNoticeContentService.add(taskNoticeContentDTO);
        } catch (InterruptedException e) {
            log.error("运输单退回修改未获取到锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            lock.unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void review(TaskDTO dto) {
        String lockKey = DistributedLockEnum.CROSSBORDER_REVIEW.getCode() + ":" +  dto.getDocumentId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("运输单复核未获取到锁，运输单id：【%s】，操作人：【{}】", dto.getDocumentId()),SecurityUtil.getCurrentPersonName());
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            TsfPreconditions.checkArgument(Objects.equals(DomainTypeEnum.CROSSBORDERWAYBILL.getCode(),dto.getDocumentClass()),new ServiceException("单据类型错误"));
            //操作后的状态
            CrossBorderWaybillStatusEnum afterDoneStatus = CrossBorderWaybillStatusEnum.of(dto.getNewStatusCode());
            CrossBorderWaybill crossBorderWaybill = commonService.changeDocumentStatus(dto);
            //发送通知
            TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
            taskNoticeContentDTO.setDocumentType(DomainTypeEnum.CROSSBORDERWAYBILL.getCode());
            taskNoticeContentDTO.setDocumentId(crossBorderWaybill.getId().toString());
            BillTypeEnum billTypeEnum = BillTypeEnum.of(crossBorderWaybill.getBillType());
            //未知客户
            taskNoticeContentDTO.setCustomerId(0L);
            switch (afterDoneStatus) {
                case WAIT_BIND: //退回绑单
                    DomainOprationEnum oprationEnum = Objects.equals(billTypeEnum,BillTypeEnum.IMP)?DomainOprationEnum.CROSS_BORDER_WAYBILL_BIND:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_BIND;
                    taskNoticeContentDTO.setOperationCode(oprationEnum.getCode());
                    taskNoticeContentDTO.setContent(String.format("运输单【%s】复核时被退回重新绑单，提交人【%s】", crossBorderWaybill.getDocNo(),SecurityUtil.getCurrentPersonName()));
                    taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",crossBorderWaybill.getDocNo()));
                    break;
                case WAIT_DEPART: //待发车
                    DomainOprationEnum domainOprationEnum = Objects.equals(billTypeEnum,BillTypeEnum.IMP)?DomainOprationEnum.CROSS_BORDER_WAYBILL_DEPART:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_DEPART;
                    taskNoticeContentDTO.setOperationCode(domainOprationEnum.getCode());
                    taskNoticeContentDTO.setContent(String.format("【%s】提交了一份跨境运输单【%s】需要您确认是否发车", SecurityUtil.getCurrentPersonName(),crossBorderWaybill.getDocNo()));
                    taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",crossBorderWaybill.getDocNo()));
                    break;
                default:
                    throw new ServiceException("当前运输单状态不允许此操作");
            }
            taskNoticeContentService.add(taskNoticeContentDTO);
        } catch (InterruptedException e) {
            log.error("跨境运输单复核操作获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            lock.unlock();
        }
    }

    private static final Map<DomainOprationEnum,List<CrossBorderWaybillStatusEnum>> ALLOW_STATUS_MAP = new LinkedHashMap<DomainOprationEnum,List<CrossBorderWaybillStatusEnum>>(){{
        //跨境运输发车
        put(DomainOprationEnum.CROSS_BORDER_WAYBILL_DEPART,Arrays.asList(CrossBorderWaybillStatusEnum.WAIT_REVIEW,CrossBorderWaybillStatusEnum.WAIT_SIGN));
        put(DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_DEPART,Arrays.asList(CrossBorderWaybillStatusEnum.WAIT_REVIEW,CrossBorderWaybillStatusEnum.WAIT_SIGN));
    }};

    //验证操作
    private static void verifyOperation(TaskDTO dto){
        List<CrossBorderWaybillStatusEnum> cbseList = ALLOW_STATUS_MAP.get(DomainOprationEnum.of(dto.getOperation()));
        TsfPreconditions.checkArgument(CollUtil.isNotEmpty(cbseList),new ServiceException("不支持的操作,请刷新页面"));
        TsfPreconditions.checkArgument(cbseList.contains(CrossBorderWaybillStatusEnum.of(dto.getNewStatusCode())),new ServiceException("当前状态不支持此操作,请刷新页面"));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processJump(TaskDTO dto) {
        verifyOperation(dto);
        commonService.changeDocumentStatus(dto);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void depart(CrossBorderWaybillDepartDTO dto) {
        String lockKey = DistributedLockEnum.CROSSBORDER_DEPART.getCode() + ":"  + dto.getId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("运输单发车未获取到锁，运输单id：【%s】，操作人：【{}】", dto.getId()),SecurityUtil.getCurrentPersonName());
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            CrossBorderWaybill crossBorderWaybill = super.getById(dto.getId());
            TsfPreconditions.checkArgument(Objects.nonNull(crossBorderWaybill),new ServiceException("运单不存在"));
            BillTypeEnum billTypeEnum = BillTypeEnum.of(crossBorderWaybill.getBillType());
            List<String> orderNos = Lists.newArrayList();
            switch (billTypeEnum){
                case IMP:
                    List<BindOrderVO> bindedOrders = impOrderService.waybillBindedOrderList(crossBorderWaybill.getDocNo());
                    bindedOrders.stream().forEach(bindOrder -> {
                        //验证报关状态
                        DeclarationStatusEnum declarationStatusEnum = DeclarationStatusEnum.of(bindOrder.getDeclareStatusId());
                        TsfPreconditions.checkArgument(Objects.equals(DeclarationStatusEnum.COMPLETE,declarationStatusEnum),new ServiceException(String.format("订单【%s】还未申报完成无法发车确认",StringUtils.removeSpecialSymbol(bindOrder.getDocNo()))));

                        //检查订单对应的报关单的进境关别和跨境运输单是否一致
                        TsfPreconditions.checkArgument(Objects.equals(bindOrder.getIePortCode(),crossBorderWaybill.getCustomsCode()),new ServiceException(String.format("订单【%s】对应的报关单进境关别【%s】与运输单通关口岸【%s】不一致，不能绑定到该运输单，请解绑该订单再继续操作",bindOrder.getDocNo(),bindOrder.getIePortName(),crossBorderWaybill.getCustomsName())));

                        //已绑定的订单号
                        if(orderNos.contains(bindOrder.getDocNo())){
                            throw new ServiceException(String.format("订单号【%s】绑定重复，请解绑",bindOrder.getDocNo()));
                        }
                        orderNos.add(bindOrder.getDocNo());

                        //处理国内送货单
                        deliveryWaybillService.driveByCrossBorderWaybill(crossBorderWaybill,bindOrder.getId());
                    });
                    //生成香港出库单
                    deliveryNoteService.overseasDeliveryOrder(orderNos,dto.getDepartureDate());
                    break;
                case EXP:
                    List<BindOrderVO> expBindedOrders = expOrderService.waybillBindedOrderList(crossBorderWaybill.getDocNo());
                    expBindedOrders.stream().forEach(bindOrder -> {
                        //验证报关状态
                        DeclarationStatusEnum declarationStatusEnum = DeclarationStatusEnum.of(bindOrder.getDeclareStatusId());
                        TsfPreconditions.checkArgument(Objects.equals(DeclarationStatusEnum.COMPLETE,declarationStatusEnum),new ServiceException(String.format("订单【%s】还未申报完成无法发车确认",StringUtils.removeSpecialSymbol(bindOrder.getDocNo()))));

                        //检查订单对应的报关单的进境关别和跨境运输单是否一致
                        TsfPreconditions.checkArgument(Objects.equals(bindOrder.getIePortCode(),crossBorderWaybill.getCustomsCode()),new ServiceException(String.format("订单【%s】对应的报关单出境关别【%s】与运输单通关口岸【%s】不一致，不能绑定到该运输单，请解绑该订单再继续操作",bindOrder.getDocNo(),bindOrder.getIePortName(),crossBorderWaybill.getCustomsName())));

                        //已绑定的订单号
                        if(orderNos.contains(bindOrder.getDocNo())){
                            throw new ServiceException(String.format("订单号【%s】绑定重复，请解绑",bindOrder.getDocNo()));
                        }
                        orderNos.add(bindOrder.getDocNo());
                    });
                    break;
                default:
                    throw new ServiceException("单据类型错误");
            }

            //操作后的状态
            TaskDTO taskDTO = new TaskDTO();
            taskDTO.setDocumentId(dto.getId());
            taskDTO.setDocumentClass(DomainTypeEnum.CROSSBORDERWAYBILL.getCode());
            DomainOprationEnum domainOprationEnum = Objects.equals(billTypeEnum,BillTypeEnum.IMP)?DomainOprationEnum.CROSS_BORDER_WAYBILL_DEPART:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_DEPART;
            taskDTO.setOperation(domainOprationEnum.getCode());
            taskDTO.setNewStatusCode(CrossBorderWaybillStatusEnum.WAIT_SIGN.getCode());
            CrossBorderWaybill update = commonService.changeDocumentStatus(taskDTO);
            //修改运输单上的发车时间和封条号
            update.setDepartureDate(dto.getDepartureDate());
            update.setSealNo(dto.getSealNo());
            super.updateById(update);
            //发送通知
            TaskNoticeContentDTO taskNoticeContentDTO = new TaskNoticeContentDTO();
            taskNoticeContentDTO.setDocumentType(DomainTypeEnum.CROSSBORDERWAYBILL.getCode());
            taskNoticeContentDTO.setDocumentId(crossBorderWaybill.getId().toString());
            taskNoticeContentDTO.setCustomerId(0L);//未知客户
            DomainOprationEnum oprationEnum = Objects.equals(billTypeEnum,BillTypeEnum.IMP)?DomainOprationEnum.CROSS_BORDER_WAYBILL_SIGN:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_SIGN;
            taskNoticeContentDTO.setOperationCode(oprationEnum.getCode());
            taskNoticeContentDTO.setContent(String.format("跨境运输单【%s】已发车，到达后请确认签收。",crossBorderWaybill.getDocNo()));
            taskNoticeContentDTO.setQueryParamsMap(ImmutableMap.of("docNo",crossBorderWaybill.getDocNo()));
            taskNoticeContentService.add(taskNoticeContentDTO);

        } catch (InterruptedException e) {
            log.error("跨境运输单发车操作获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            lock.unlock();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void sign(CrossBorderWaybillSignDTO dto) {
        String lockKey = DistributedLockEnum.CROSSBORDER_SIGN.getCode() + ":" + dto.getId();
        Lock lock = redisLockRegistry.obtain(lockKey);
        boolean isLock;
        try {
            isLock = lock.tryLock(lockTime, TimeUnit.SECONDS);
            if (!isLock) {
                log.error(String.format("运输单签收未获取到锁，运输单id：【%s】，操作人：【{}】", dto.getId()),SecurityUtil.getCurrentPersonName());
                throw new ServiceException("服务拥挤，请稍后再试");
            }
            CrossBorderWaybill waybill = super.getById(dto.getId());
            TsfPreconditions.checkArgument(Objects.nonNull(waybill),new ServiceException("运单不存在"));
            BillTypeEnum billTypeEnum = BillTypeEnum.of(waybill.getBillType());

            switch (billTypeEnum){
                case IMP:
                    //获得绑定的订单
                    List<BindOrderVO> bindedOrders = impOrderService.waybillBindedOrderList(waybill.getDocNo());
                    //同步订单已通关
                    impOrderService.impOrderClearance(bindedOrders);
                    //货运指定地点
                    TransportDestinationEnum destinationEnum = TransportDestinationEnum.of(waybill.getTransportDestination());
                    if(Objects.equals(TransportDestinationEnum.DESTINATION,destinationEnum)){
                        bindedOrders.stream().forEach(bindOrder -> {
                            //处理国内送货单
                            deliveryWaybillService.signByCrossBorderWaybill(waybill,bindOrder.getId());
                        });
                    }
                    break;
                case EXP:
                    //获得绑定的订单
                    List<BindOrderVO> expBindedOrders = expOrderService.waybillBindedOrderList(waybill.getDocNo());
                    //同步订单已通关
                    expOrderService.expOrderClearance(expBindedOrders);
                    break;
                default:
                    throw new ServiceException("单据类型错误");
            }

            //操作后的状态
            TaskDTO taskDTO = new TaskDTO();
            taskDTO.setDocumentId(dto.getId());
            taskDTO.setDocumentClass(DomainTypeEnum.CROSSBORDERWAYBILL.getCode());
            DomainOprationEnum oprationEnum = Objects.equals(billTypeEnum,BillTypeEnum.IMP)?DomainOprationEnum.CROSS_BORDER_WAYBILL_SIGN:DomainOprationEnum.EXP_CROSS_BORDER_WAYBILL_SIGN;
            taskDTO.setOperation(oprationEnum.getCode());
            taskDTO.setNewStatusCode(CrossBorderWaybillStatusEnum.SIGNED.getCode());
            CrossBorderWaybill crossBorderWaybill = commonService.changeDocumentStatus(taskDTO);
            crossBorderWaybill.setReachDate(dto.getReachDate());// 到达日期
            super.updateById(crossBorderWaybill);

        } catch (InterruptedException e) {
            log.error("跨境运输单签收操作获取锁异常",e);
            throw new ServiceException("服务拥挤，请稍后再试");
        } finally {
            lock.unlock();
        }
    }

    @Override
    public CrossBorderWaybill findByImpOrderId(Long orderId) {
        return crossBorderWaybillMapper.findByImpOrderId(orderId);
    }

    @Override
    public CrossBorderWaybill findByExpOrderId(Long orderId) {
        return crossBorderWaybillMapper.findByExpOrderId(orderId);
    }

    @Override
    public Integer impTrainNumber(Date startDate, Date endDate) {
        endDate = DateUtils.parse(DateUtils.formatShort(endDate).concat(" 23:59:59"),"yyyy-MM-dd HH:mm:ss");
        return crossBorderWaybillMapper.totalTrainNumber(BillTypeEnum.IMP.getCode(),startDate,endDate);
    }

    @Override
    public Integer expTrainNumber(Date startDate, Date endDate) {
        endDate = DateUtils.parse(DateUtils.formatShort(endDate).concat(" 23:59:59"),"yyyy-MM-dd HH:mm:ss");
        return crossBorderWaybillMapper.totalTrainNumber(BillTypeEnum.EXP.getCode(),startDate,endDate);
    }
}
